.\" Automatically generated by Pod::Man 4.07 (Pod::Simple 3.32) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" Set up some character translations and predefined strings. \*(-- will .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left .\" double quote, and \*(R" will give a right double quote. \*(C+ will .\" give a nicer C++. Capital omega is used to do unbreakable dashes and .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, .\" nothing in troff, for use with C<>. .tr \(*W- .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' .ie n \{\ . ds -- \(*W- . ds PI pi . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch . ds L" "" . ds R" "" . ds C` "" . ds C' "" 'br\} .el\{\ . ds -- \|\(em\| . ds PI \(*p . ds L" `` . ds R" '' . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .if !\nF .nr F 0 .if \nF>0 \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{\ . nr % 0 . nr F 2 . \} .\} .\" ======================================================================== .\" .IX Title "Rinci::Undo 3pm" .TH Rinci::Undo 3pm "2016-12-28" "perl v5.24.1" "User Contributed Perl Documentation" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH "NAME" Rinci::Undo \- (DEPRECATED) Protocol for undo operations in functions .SH "VERSION" .IX Header "VERSION" This document describes version 1.1.84 of Rinci::Undo (from Perl distribution Rinci), released on 2016\-12\-28. .SH "SPECIFICATION VERSION" .IX Header "SPECIFICATION VERSION" .Vb 1 \& 1.1 .Ve .SH "STATUS" .IX Header "STATUS" This protocol (riundo for short) is now deprecated in favor of Rinci::Transaction (ritx for short) for several reasons: .IP "\(bu" 4 riundo is inherently unreliable .Sp Undo information is returned by function \fIafter\fR the function has performed the action. If function dies in the middle of action, client does not have the information to undo the (partially completed) action. That is why in ritx, the \&\s-1TM\s0 asks the function first for undo information before asking the function to perform its action. .IP "\(bu" 4 ritx does not limit using the same function for undo .Sp In riundo, we must call the same function (passing the previously obtained undo data from the that function) to undo the information. This is sometimes slightly cumbersome. The undo action might be provided by other functions, but we still have to go through the same function first. .IP "\(bu" 4 ritx can also implement undo/redo .Sp So there is no need for maintaining two specifications. .SH "SPECIFICATION" .IX Header "SPECIFICATION" This document describes the Rinci undo protocol. This protocol must be followed by functions that claim that they support undo (have their \f(CW\*(C`undo\*(C'\fR \f(CW\*(C`feature\*(C'\fR set to true). Such functions are from here on called \fIundoable function\fR (or just function, unless when ambiguous). .PP The protocol is basically the non-OO version of the command pattern, a design pattern most commonly used to implement undo/redo functionality. In this case, each function behaves like a command object. You pass a special argument \&\f(CW\*(C`\-undo_action\*(C'\fR with the value of \f(CW\*(C`do\*(C'\fR and \f(CW\*(C`undo\*(C'\fR to execute or undo a command, respectively. For \f(CW\*(C`do\*(C'\fR and \f(CW\*(C`undo\*(C'\fR, the same set of arguments are passed. .SS "Requirements" .IX Subsection "Requirements" Function \s-1MUST\s0 check special argument \f(CW\*(C`\-undo_action\*(C'\fR before it checks other arguments. Function \s-1MUST\s0 at least support the following undo action: \f(CW\*(C`do\*(C'\fR, \&\f(CW\*(C`undo\*(C'\fR. On unsupported/unknown undo action, function \s-1MUST\s0 return status 400, with message like \*(L"Unsupported undo action\*(R". .PP If \f(CW\*(C`\-undo_action\*(C'\fR is not set, it means caller does not care about undo. Undoable function should execute as any normal function. .SS "Performing 'do'" .IX Subsection "Performing 'do'" To indicate that we need undo, we call function by passing special argument \&\f(CW\*(C`\-undo_action\*(C'\fR with the value of \f(CW\*(C`do\*(C'\fR. Function should perform its operation and save undo data along the way. If \f(CW\*(C`\-undo_action\*(C'\fR is not passed or false/undef, function should assume that caller does not need undo later, so function need not save any undo data. After completing operation successfully, function should return status 200, the result, and undo data. Undo data is returned in the result metadata (the fourth element of result envelope), example: .PP .Vb 1 \& [200, "OK", $result, {undo_data=>$undo_data}] .Ve .PP Undo data should be serializable so it is easy to be made persistent if necessary (e.g. by some undo/transaction manager). .SS "Performing 'undo'" .IX Subsection "Performing 'undo'" To perform an undo, caller must call the function again with the same previous arguments, except \f(CW\*(C`\-undo_action\*(C'\fR should be set to \f(CW\*(C`undo\*(C'\fR and \f(CW\*(C`\-undo_data\*(C'\fR set to undo data previously given by the function. Function should perform the undo operation using the undo data. Upon success, it must return status 200, the result, and an undo data (in other words, redo data, since it can be used to undo the undo operation). .SS "Performing 'redo'" .IX Subsection "Performing 'redo'" To perform redo, caller can call the function again with <\-undo_action> set to \&\f(CW\*(C`undo\*(C'\fR and \f(CW\*(C`\-undo_data\*(C'\fR set to the redo data given in the undo step. Or, alternatively, caller can just perform a normal do (see above). .PP An example: .PP .Vb 10 \& $SPEC{setenv} = { \& v => 1.1, \& summary => \*(AqSet environment variable\*(Aq, \& args => { \& name => {req=>1, schema=>\*(Aqstr*\*(Aq}, \& value => {req=>1, schema=>\*(Aqstr*\*(Aq}, \& }, \& features => {undo=>1}, \& }; \& sub setenv { \& my %args = @_; \& my $name = $args{name}; \& my $value = $args{value}; \& my $undo_action = $args{\-undo_action} // \*(Aq\*(Aq; \& my $undo_data = $args{\-undo_data}; \& \& my $old; \& if ($undo_action) { \& # save original value and existence state \& $old = [exists($ENV{$name}), $ENV{$name}]; \& } \& \& if ($undo_action eq \*(Aqundo\*(Aq) { \& if ($undo_data\->[0]) { \& $ENV{$name} = $undo_data\->[1]; \& } else { \& delete $ENV{$name}; \& } \& } else { \& $ENV{$name} = $value; \& } \& \& [200, "OK", undef, $undo_action ? {undo_data=>$old} : {}]; \& } .Ve .PP The above example declares an undoable command \f(CW\*(C`setenv\*(C'\fR to set an environment variable (\f(CW%ENV\fR). .PP To perform command: .PP .Vb 3 \& my $res = setenv(name=>"DEBUG", value=>1, \-undo_action=>"do"); \& die "Failed: $res\->[0] \- $res\->[1]" unless $res\->[0] == 200; \& my $undo_data = $res\->[3]{undo_data}; .Ve .PP To perform undo: .PP .Vb 3 \& $res = setenv(name=>"DEBUG", value=>1, \& \-undo_action="undo", \-undo_data=>$undo_data); \& die "Can\*(Aqt undo: $res\->[0] \- $res\->[1]" unless $res\->[0] == 200; .Ve .PP After this undo, \s-1DEBUG\s0 environment variable will be set to original value. If it did not exist previously, it will be deleted. .PP To perform redo: .PP .Vb 3 \& my $redo_data = $res\->[3]{undo_data}; \& $res = setenv(name=>"DEBUG", value=>1, \& \-undo_action="undo", \-undo_data=>$redo_data); .Ve .PP or you can just do: .PP .Vb 1 \& $res = setenv(name=>"DEBUG", value=>1, \-undo_action="do"); .Ve .SS "Saving undo data in external storage" .IX Subsection "Saving undo data in external storage" Although the complete undo data can be returned by the function in the \&\f(CW\*(C`undo_data\*(C'\fR result metadata property, sometimes it is more efficient to just return a pointer to said undo data, while saving the actual undo data in some external storage. .PP For example, if a function deletes a big file and wants to save undo data, it is more efficient to move the file to trash directory and return its path as the undo data, instead of reading the whole file content and its metadata to memory and return it in \f(CW\*(C`undo_data\*(C'\fR result metadata. .PP Functions which require undo trash directory should specify this in its metadata, through the \f(CW\*(C`undo_trash_dir\*(C'\fR dependency clause. For example: .PP .Vb 4 \& deps => { \& ... \& trash_dir => 1, \& } .Ve .PP When calling function, caller needs to provide path to undo trash directory via special argument \f(CW\*(C`\-trash_dir\*(C'\fR, for example: .PP .Vb 1 \& \-trash_dir => "/home/.trash/2fe2f4ad\-a494\-0044\-b2e0\-94b2b338056e" .Ve .SS "What about non-undoable actions?" .IX Subsection "What about non-undoable actions?" Like in real life, not all actions are undoable. Examples of undoable/irreversible actions include wiping a file/directory (more generally speaking, any action to permanently delete/destroy something, without backing up the data first), sending an email (more generally speaking, any action that is sent to an external entity beyond our control, unless that external entity provides a way to undo the action). .PP An undoable function \s-1MUST NOT\s0 mix undoable and non-undoable actions. For example: .PP .Vb 2 \& safe_delete(file=>\*(Aq/path/to/file\*(Aq); # puts file into Trash, undoable action \& safe_delete(file=>\*(Aq/path/to/file\*(Aq, permanent=>1); # deletes file, non\-undoable .Ve .PP The \f(CW\*(C`safe_delete\*(C'\fR function above mixes undoable action (putting a file into Trash directory) and non-undoable action (permanently deleting a file without putting it in Trash). Without domain knowledge of the function, a caller cannot know whether a call will be undoable or not. This will also prevent the function from participating in a transaction, because transaction requires function call to always be undoable, for rollback purpose. .PP The solution is to separate non-undoable action in another function, for example: .PP .Vb 3 \& trash(file=>\*(Aq/path/to/file\*(Aq); # undoable, can execute inside transaction \& delete(file=>\*(Aq/path/to/file\*(Aq); # non\-undoable, executes outside transaction \& empty_trash(); # non\-undoable, executes outside transaction .Ve .PP The non-undoable function is also non-transactional (it operates outside the scope of a transaction). But it can still be idempotent. And it can manipulate the transactions if it needs too. In the example, the \fIempty_trash()\fR function instructs the transaction manager to discard the \fItrash()\fR transactions, since after the trash is emptied, the \fItrash()\fR transactions cannot be undone anyway. .SH "HOMEPAGE" .IX Header "HOMEPAGE" Please visit the project's homepage at . .SH "SOURCE" .IX Header "SOURCE" Source repository is at . .SH "BUGS" .IX Header "BUGS" Please report any bugs or feature requests on the bugtracker website .PP When submitting a bug or request, please include a test-file or a patch to an existing test-file that illustrates the bug or desired feature. .SH "SEE ALSO" .IX Header "SEE ALSO" Related specifications: Rinci::Transaction .SH "AUTHOR" .IX Header "AUTHOR" perlancar .SH "COPYRIGHT AND LICENSE" .IX Header "COPYRIGHT AND LICENSE" This software is copyright (c) 2016 by perlancar@cpan.org. .PP This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.