• 削除の同期はどうやればいいのか?

    By Muneyuki Ohkawa 2 decades ago

    Notesの複製のように、削除スタブがあるわけではないので、どういうふうに実装したらいいのだろう?

    片一方にしかないカレンダー項目があった場合、それは新規で作られたものなのか、同期後に片一方が削除されて残ったものなのか、判別させることができるのだろうか?

    • 同期方法の案

      By Hirotaka Nagashima 2 decades ago

      僕が思うに、GooCalSync側で何らかの一覧を持っていないと適切にハンドル出来ないのではないか、と考えています。



       Notes側も削除スタブからその文書が元々カレンダーであったか通常のメールであったかを判別する事は出来ないので、どちらにしても削除スタブに依存したアーキテクチャには出来ないので。

       そもそもメールテンプレートに入れ込むのか、別DBとして実装するのか単に単体ツールとして実装するか決まってないので提案しづらいのですが、たたき台があったほうがいいと思うので、案を出します。



      同期方法素案



      今以下のようなスケジュールを想定します。

      Notes側:

      1. 4/21 15:00-16:00 Meeting

            NoteID:NT0000BBBB (Appointment)
      2. 4/21 17:00-18:00 Tennnis

             NoteID:NT0000AAAA (Appointment)



        Google

        2' 4/21 17:00-18:00 Soccer(Appointment)
      3. 4/22 有給 (Anniversary)



        (1) Notes側のデータから一覧を作成

        このとき、GooCalSync側ではまず、Notes側のカレンダーから以下のような一覧を保持するようにします。(このテーブルをReplication Table と呼びます)



        Replication Table:

        NT0000BBBB :4/21 15:00-16:00 :Meeting : Appointment : Not-Replicated

        NT0000AAAA : 4/21 17:00-18:00 :Tennis : Appointment : Not-Replicated



        (2) Google と同期 (新規作成)

         ここで、NT0000BBBB のスケジュールは同じタイムレンジにスケジュールが無いので、Google Calendar側に新規スケジュールが必要と判断し、4/21 15:00-16:00 :Meeting の予定を新規に作成します。



        Replication Table:

        NT0000BBBB :4/21 15:00-16:00 :Meeting : Appointment : 2009/04/17 12:05:00(複製時刻)

        NT0000AAAA : 4/21 17:00-18:00 :Tennis : Appointment : Not-Replicated



        (3) Google Calendar とマージ(衝突)

        NT0000AAAA のスケジュールはGoogle Calendarの Soccerのスケジュールとタイムレンジが同じかつ同じイベントタイプであるため衝突とみなされます。

        以下のオプションを選べるようにする事にします。


      4. 常にNotesを優先
      5. 常に重複するエントリを作成
      6. プロンプトをして優先するエントリを選択



        以下の場合は衝突と見なさず、新規にエントリを作成します。


      7. タイムレンジは同じだがイベントタイプが違う場合
      8. タイムレンジは重なるが異なる場合

         (Soccerは17:00-19:00の場合など)



        ここではNotes優先とすることにしますが、結果以下のような状態になります。



        Replication Table:

        NT0000BBBB :4/21 15:00-16:00 :Meeting : Appointment : 2009/04/17 12:05:00(複製時刻)

        NT0000AAAA : 4/21 17:00-18:00 :Tennis : Appointment : 2009/04/17 12:05:01(複製時刻)



        (4) Google Calendar からPULLする

        4/22 有給 はGoogle Calendarには無いので新規に作成します。

        このとき作成後のNoteIDもGooCalSyncに記録します。ここでは(3) でNotes優先を選んだとして最終状態は以下のようになります。



        Replication Table:

        NT0000BBBB :4/21 15:00-16:00 :Meeting : Appointment : 2009/04/17 12:05:00(複製時刻)

        NT0000AAAA : 4/21 17:00-18:00 :Tennis : Appointment : 2009/04/17 12:05:01(複製時刻)

        NT0000CCCC :4/22 : 有給 : Anniversary : 2009/04/17 12:05:02(複製時刻)



        Notes

        4/21 15:00-16:00 Meeting NoteID:NT0000BBBB (Appointment)

        4/21 17:00-18:00 Tennnis NoteID:NT0000AAAA (Appointment)

        4/22 有給 NoteID:NT0000CCCC (Anniversary)



        Google

        4/21 17:00-18:00 Tennis(Appointment)

        4/22 有給 (Anniversary)





        (5) 競合の検出と判定

        以下の方法で各カレンダーの最終更新日を検出します。

        NotesDocumentクラスのLastModified(またはDocumentクラスのgetLastModified() を使用)

        Google Calendar: BaseEntryクラスのgetUpdated() メソッド

        複製時刻と比較し、以下の動作を行います。



        片方だけが更新されている場合:上書き

        両方が更新されている場合: 以下のオプションより選択
      9. 常にNotes側が優先
      10. 常にGoogle側が優先
      11. プロンプトしてどちらを優先するか決定する



        (6) 削除の判定とマージ

         削除は以下のように判定します。



        Notes側:

        Replication TableにあるNoteIDがNotes側に存在しない



        Google Calendar側:

         同じタイムレンジかつイベントタイプのスケジュールが存在しない

        (タイムレンジが変更された予定は新規スケジュールと見なされます)





        このとき削除をどう扱うかは以下のオプションより選ぶ事にします。
      12. Notes側の削除のみ反映
      13. Google Calendar側の削除のみ反映
      14. 削除された側が更新されていた場合、削除をしない
      15. 削除優先(削除されたものは常に削除)


      • 同時刻に同じタイプのスケジューリングは可能にしたいのです。

        By Muneyuki Ohkawa 2 decades ago

        私のカレンダーは、同じ時間帯に同タイプのスケジュールが2つ3つ入っていることが珍しくありません。バッティングしているということですが、それを認識して、その後、スケジュール調整しています。そういう人は周りに結構います。従って、カレンダーの同一性を、時間帯とカレンダーのタイプで判断するのは避けたいと考えています。



        カレンダーの同一性は、おそらく、UNIDみたいなもので判断させることになると思っています。GoogleのカレンダーをiCal形式でエクスポートすると、"UID"という項目が見えますので、これでなんと出来そうな気がします。

    • 削除同期方法案2

      By Muneyuki Ohkawa 2 decades ago

      ポイントは、どちらかにしか無いエントリを、以下の2つのどちらなのかを、どう判断するのかだと考えました。

       1.新規エントリであり、他へのコピーが必要なもの。

       2.過去に同期されてから片方が削除されたエントリであり、削除すべきもの。



      この判断は、最終同期実施時刻(A)を覚えておき、どちらかにしか無いエントリの作成・更新日(B)と比較することで出来ると思います。

       ・BがAより古ければ、削除すべきエントリ。

       ・BがAより新しければ、コピーすべきエントリ。



      このロジックでどうでしょうか。

      • よさそうですね

        By Hirotaka Nagashima 2 decades ago

        ちょっと考えてみたのですが、よさそうですね。



        削除定義:

         ・既存エントリの最終更新日(B)が最終同期実施時刻(A)より古ければ、削除すべきエントリ。

         ・既存エントリの最終更新日(B)が最終同期実施時刻(A)より新しければ、コピーすべきエントリ。



        ロジック:

        <PRE>

        If (A > B) Then

        Delete<br/>
        

        else

        Copy (削除文書復活)<br/>
        

        End If

        </PRE>



        ただ、以下の場合は削除文書復活になるのではないかと思います。



        (削除文書復活のケース)

        AM 08:00 : 同期(A)

        AM 09:00 :会議の予定削除 (Notes)

        AM 09:30 :会議の予定編集 (Google Calendar) :B

        AM 10:00 :同期(A')





        一人で使う事が想定されるカレンダーでこの状況が起こるのは十中八九ヒューマンエラーだと考えられるので、消えてしまうよりこの動作の方が望ましいと思うのですが、やはり削除同期のオプションはあったほうがいいです。

        (常にNotes/Google優先、常に削除優先 etc…)

        • 確かに削除復活になりますね。

          By Muneyuki Ohkawa 2 decades ago

          では、最終更新日ではなく、作成日で判断させるのはどうでしょうか。これなら、提示いただいたケースでの削除の復活はなくなります。BaseEntryクラスのJavaDocを見てみると、getPublished()、getUpdated()というメソッドが用意されています。Google Calendar Data APIは、他システムとの同期に利用されることを考えられて作られているようなので、ちゃんとこのようなプロパティがあるんですね。



          ただ、おっしゃるように、削除の動作ロジックを決めるオプション設定は用意する必要はありますね。更新がバッティングした場合に、どっちを優先させるかというオプションも同様に必要そうです。

      • 最終同期時刻は必要ですね

        By Junya Terada 2 decades ago

        勝手な想像のため、どんどん指摘ください。(性格上細かく考えるのが苦手なため、細かいケースを考えていません。)



        <( )>



        –Logic–(処理順序とは関係ありません。)

        削除or追加の判断方法

        Notes→Googleへ反映:

         -Notesの一覧とGoogleの一覧を比較

          ・NotesにあってGoogleに無いものを追加

           ※追加の条件:最終同期時刻(A)とNotesのカレンダーの更新(or作成)時刻(B)でA
          ・GoogleにあってNotesに無いものを削除

           ※削除の条件:GoogleのカレンダーでNotesとの同期で追加されたもの(判定方法はUNIDを用いる)



        Google→Notesへの反映:

         -Googleの一覧とNotesの一覧を比較

          ・GoogleにあってNotesに無いものを追加

           ※追加の条件:GoogleカレンダーにUNIDがアサインされていないもの

          ・NotesにあってGoogleに無いものを削除

           ※削除の条件:最終同期時刻(A)とNotesのカレンダーの更新(or作成)時刻(B)でA>B

        • 間違い訂正です。

          By Junya Terada 2 decades ago

          すみません、中途半端で止まっていたので、訂正です。



            ・GoogleにあってNotesに無いものを削除

             ※削除の条件:GoogleのカレンダーでNotesとの同期で追加されたもの(判定方法はUNIDを用いる)







            ・GoogleにあってNotesに無いものを削除

             ※削除の条件:Notesとの同期で追加されたGoogleのカレンダー(UNIDがすでに割り振られたGoogleカレンダー)

          • Notes UNIDなのか Google UIDなのかの判断

            By Muneyuki Ohkawa 2 decades ago

            Googleカレンダーエントリの追加 or 削除の判断を、UNIDがアサインされているかどうかで決まるロジックになっていますが、この判断が技術的に難しいと思います。というのは、Google側で手動でカレンダーを追加すると、UIDにはGoogleが自動的に割り振る値がセットされますが、これがGoogleがセットしたのものなのか、GooCalSyncによってセットされたNotes UNIDなのか、判別ロジックが確立できないと思います。桁数の違いなどを利用することも考えられますが、Google側の仕様が変わるかもしれないですし、ちょっと無理かなと思います。



            Notes側のカレンダーエントリの追加 or 削除の判断を、最終同期日時との比較で行っているので、Google側も同じように判断するのがシンプルかつ統一性のあるロジックだと思うのですが、いかがでしょうか。

            • 最終更新日時を使うので良いと思います。

              By Junya Terada 2 decades ago

              決めの問題だと思っていたので、更新日時を利用するので良いと思います。



              ただ、1点疑問があるのですが、IcalUIDをどのように使っているか分からなく、ざっと調べてみた感じOutlookとかの同期に使う?ような感じだったので、Notes→Googleの時にUNIDをつかってIcalUIDを更新しても良いと思ったのですが、Googleで作られたカレンダーのIcalUIDを更新しても大丈夫なのかなと・・・・



              iPhoneの同期は何をキーにしているのでしょうね・・・・

              • Google側でアサインされたiCal UIDの変更

                By Muneyuki Ohkawa 2 decades ago

                これは大丈夫だと思います。

                例えば、NotesのカレンダーをiCal形式でエクスポートして、Googleにインポートすると、Notes側で作成されたiCal UIDがそのまま入ります。つまり、Google側でアサインされたものしか使えないわけではないということです。



                iCal UIDがどのように使われているかですが、いろんなところで使われているでしょう。データをユニークに決定する情報は、どのようなシステムにも必要であり、カレンダーデータの場合は、標準仕様としてiCal UIDが用意されているという理解です。ユニーク性を担保するキーは他にないですから、GoogleカレンダーとiPhoneの同期を行う各種ツールも、iCal UIDを使っているものと思われます。

                • 了解いたしました。

                  By Junya Terada 2 decades ago

                  ありがとうございます。



                  では、前提として、新規のカレンダー登録は、

                  ・Notesから

                  ・Googleで作成し一番初めにNotesと同期させる



                  ということにしましょう。



                  ※下記の使い方は想定外の使い方として正しい同期がされないということで。

                  ・Googleで作成し、他のツールと同期を先にしてしまう

                  ・他のツールでカレンダーを作成しGoogleと同期させる

                  →両方のケースで、NotesとGoogleの同期後新しいカレンダーが他のツールに登録される(IcalUIDが途中で変更されるため)

                  • Google側のiCal UIDの変更影響

                    By Muneyuki Ohkawa 2 decades ago

                    寺田さん、ご指摘ありがとうございます。

                    他の同期ツールが、GooCalSyncと同様に、iCal UIDを変更するかもしれないことを懸念されていたのですね。

                    その点は、私は気づかなかったです。確かにおっしゃるとおりだと思います。

                    そう考えると、ExtendedProperty を使って、Notes UNIDを持たせたほうがいいですね。また、「iPhone上のカレンダアプリで新規作成して、Googleに転送し、GooCalSyncでNotesに転送」 ということを考えると、最後にiCal UIDが変わることになり、その後のiPhoneカレンダアプリとの同期が出来なくなりそうです。



                    標準で用意されたプロパティの範囲で実装するほうがリスクが少ないと思って、ExtendedPropertyは使わないほうがいいと思っていました。しかし、ExtendedPropertyを使って、他ツールとの依存性を完全に排除したほうがよさそうです。他ツールがExptendedPropertyを削除してしまうということも考えられなくはないですが・・・。



                    ということで、iCal UIDの変更はやめて、ExtendedPropertyでNotes UIDを持たせるようにしましょう。

                    • すごく悩んでいます。

                      By Junya Terada 2 decades ago

                      UNIDとIcalUIDのどっちをキーにするかですね・・・・



                      ExtendedPropertyも消される可能性が高いので、すごく迷ってて最近結局他のアプリ(ツール)に見習って、

                      NotesのカレンダーにIcalUIDを持ったほうが良いのではと思ってきていました・・・・



                      設計の根幹にかかわるところなので、ここはいろいろ意見を聞いてすり合わせをしたいなと思っています。



                      今のところ、

                      ・IcalUIDをNotesのUNIDで上書き→メリット:標準フィールドのみ利用 デメリット:他のツールから作成したエントリ処理問題

                      ・Googleカレンダーの拡張プロパティーにUNID→メリット:他のツールとの同期が可能 デメリット:他のツールでGoogleカレンダーの拡張プロパティーを消される



                      の2案ですが、最近考え始めた構想です。

                      ・Notesのカレンダーに表示フィールドとしないが、無理やり、IcalUIDをカレンダーの文書に格納してしまう。メリット→標準フィールドのみ利用 デメリット:表示しないフィールドなため、同期がうまく行かなかったとき修正がたいへん



                      で、最後の最近考え始めた案を実現する方法としては、Notesカレンダーに直にIcalUIDをセットしてしまう or 今回作成予定NSFでUNIDとIcalUIDの紐付け文書を持つことにするのか かなと思っています。



                      その他皆様が考えている意見をお聞かせください。

                      • Notesの文書はユーザーがコピーできるので

                        By Toshiaki Nakamura 2 decades ago

                        私はNotesのカレンダーの文書にIDを格納するのは賛同できないですね。



                        と言うのも、Notesの文書はユーザー自身がコピーとペーストを出来るようになっています。つまり、同じIDを保持する文書が複数できてしまう恐れがあります。



                        むしろ、削除スタブとは違いますが、ToolのDB自体に、NotesとGoogleの双方のIDを保持する文書をイベントの数だけ持たせるほうが現実的と思います。

                        ただ、この場合、ToolのDBサイズが大きくなってしまうというデメリットはあるのですが。



                        同期を行う場合、このIDを保持する文書と比較して、イベントの作成および削除を行うことになります。

                        • ご指摘の通り

                          By Junya Terada 2 decades ago

                          文書コピーはつらいですね・・・・



                          どれをとってもデメリットがついて回るのが残念ですね・・・・



                          NotesカレンダーでIcalUIDもUNIDのようにコピーしても違う値に割り振ってくれれば良いのにと思いました・・・・



                          かといって、メールのテンプレートに手を入れるのは避けたいので、↑はあきらめですね・・・・

                      • さいすけで、ExtensionPropertyが消されました。

                        By Muneyuki Ohkawa 2 decades ago

                        さいすけがExtensionPropertyをどう扱うかをテストしてみました。

                        以下の手順でテストしました。



                        Step1. GooCalSyncでGoogle上にカレンダー作成して、ExtensionPropertyをセット。

                        Step2. さいすけで同期を実行し、iPhone上にカレンダーをもってくる。

                        Step3. さいすけ上でカレンダーのタイトルを変更し、Googleとの同期を実行。

                        Step4. GooCalSyncで、ExtentionPropertyで検索。



                        結果、セットしたExtensionPropertyは削除され、Step4では検索結果がゼロでした。

                        ExtensionPropertyは、Step3で消されています。さすがに、iCal UIDは変更されていません。

                        Google上で変更する分には、ExtensionPropertyは消されていませんでした。



                        やっぱり、ExtensionPropertyは採用できませんね。

                        そうすると、GooCalSyncのNotesDB上でiCal UIDとNotes UNIDの対応表を持つ案しかないですね。



                        ■Step1でのコード

                        import com.google.gdata.client.;

                        import com.google.gdata.client.calendar.
                        ;

                        import com.google.gdata.data.;

                        import com.google.gdata.data.acl.
                        ;

                        import com.google.gdata.data.calendar.;

                        import com.google.gdata.data.extensions.
                        ;

                        import com.google.gdata.util.*;

                        import java.net.URL;



                        public class addcalext {


                        /**<br/>
                         * @param args<br/>
                         */<br/>
                        public static void main(String[] args) {<br/>
                            try {<br/>
                                <br/>
                                CalendarService myService = new CalendarService(&quot;OpenNTF-GooCalSyncTest-0.1&quot;);<br/>
                                myService.setUserCredentials(&quot;GooCalSync@gmail.com&quot;, &quot;lotus123&quot;);<br/>
                        



                        URL postUrl = new URL("http://www.google.com/calendar/feeds/GooCalSync@gmail.com/private/full");

                                EventEntry myEntry = new EventEntry();<br/>
                        


                                myEntry.setTitle(new PlainTextConstruct(&quot;ExtentionPropertyのテスト&quot;));<br/>
                                myEntry.setContent(new PlainTextConstruct(&quot;GooCalSyncで作成&quot;));<br/>
                        


                                com.google.gdata.data.DateTime startTime = com.google.gdata.data.DateTime.parseDateTime(&quot;2009-04-25T15:00:00-08:00&quot;);<br/>
                                com.google.gdata.data.DateTime endTime = com.google.gdata.data.DateTime.parseDateTime(&quot;2009-04-25T17:00:00-08:00&quot;);<br/>
                        <br/>
                                When eventTimes = new When();<br/>
                                eventTimes.setStartTime(startTime);<br/>
                                eventTimes.setEndTime(endTime);<br/>
                                myEntry.addTime(eventTimes);<br/>
                        <br/>
                               // Send the request and receive the response:<br/>
                                System.out.println(&quot;Posting calendar data....&quot;);<br/>
                                EventEntry insertedEntry = myService.insert(postUrl, myEntry);<br/>
                                <br/>
                               //Extendedプロパティの追加<br/>
                                ExtendedProperty property = new ExtendedProperty();<br/>
                                property.setName(&quot;X-GooCalSync-LotusNotesUNID&quot;);<br/>
                                property.setValue(&quot;1111222233334444AAAABBBBCCCCDDDD&quot;);<br/>
                                insertedEntry.addExtension(property);<br/>
                                insertedEntry.update();<br/>
                                <br/>
                                System.out.println(&quot;Finished!&quot;);<br/>
                        


                            } catch (ServiceException se) {<br/>
                                System.out.println(&quot;ServiceException is caught by Google Data API &quot;);<br/>
                                se.printStackTrace();<br/>
                            } catch(Exception e) {<br/>
                                e.printStackTrace();<br/>
                            }<br/>
                        




                        }<br/>
                        



                        }



                        ■Step4でのコード

                        import com.google.gdata.client.;

                        import com.google.gdata.client.calendar.
                        ;

                        import com.google.gdata.data.;

                        import com.google.gdata.data.acl.
                        ;

                        import com.google.gdata.data.calendar.;

                        import com.google.gdata.data.extensions.
                        ;

                        import com.google.gdata.util.*;

                        import java.net.URL;



                        import java.net.URL;



                        public class searchcalext {


                        public static void main(String[] args) {<br/>
                            <br/>
                            try {<br/>
                                <br/>
                                CalendarService myService = new CalendarService(&quot;OpenNTF-GooCalSyncTest-0.1&quot;);<br/>
                                myService.setUserCredentials(&quot;GooCalSync@gmail.com&quot;, &quot;lotus123&quot;);   <br/>
                                <br/>
                        

                        URL entryUrl = new URL("http://www.google.com/calendar/feeds/GooCalSync@gmail.com/private/full");

                                CalendarQuery cquery = new CalendarQuery(entryUrl);<br/>
                                cquery.setExtendedPropertyQuery(CalendarQuery.ExtendedPropertyMatch.arrayFromExtendedPropertyQueryString(&quot;[X-GooCalSync-LotusNotesUNID:1111222233334444AAAABBBBCCCCDDDD]&quot;));<br/>
                                CalendarEventFeed resultFeed = myService.query(cquery, CalendarEventFeed.class);<br/>
                                System.out.println(&quot;Extensionエントリ数:&quot; + resultFeed.getEntries().size());<br/>
                                for (int i = 0; i &lt; resultFeed.getEntries().size(); i++) {<br/>
                                    CalendarEventEntry entry = resultFeed.getEntries().get(i);<br/>
                                    System.out.println(entry.getTitle().getPlainText());<br/>
                                } <br/>
                                        <br/>
                                System.out.println(&quot;Finished!&quot;);<br/>
                        


                            } catch (ServiceException se) {<br/>
                                System.out.println(&quot;ServiceException is caught by Google Data API &quot;);<br/>
                                se.printStackTrace();<br/>
                            } catch(Exception e) {<br/>
                                e.printStackTrace();<br/>
                            }<br/>
                            <br/>
                        }<br/>
                        



                        }

                        • 対応表を持つとなるとこうなるのかな

                          By Toshiaki Nakamura 2 decades ago

                          対応表というか、おそらくは文書単位でIDを保持することになるんだと思います。



                          ひとつの文書に、Notes側とGoogle側のIDを保持しておくという前提で以下の動きが考えられますね。



                          ・最初のSyncでは、文書が存在していない。

                          まずは、Notes側からGoogleへのsyncを行い、同時に文書を作成する。

                          次に、Google側からNotesへのsyncを行い、同時に文書を作成する。



                          ・次回からのSyncでは、文書を使用する。

                          Notes側に、作成した文書に該当しないIDがあれば、Notes側からGoogleへのsyncを行い、同時に文書を作成する。

                          Google側に、作成した文書に該当しないIDがあれば、Google側からNotesへのsyncを行い、同時に文書を作成する。

                          文書を元に、Notes側にIDが存在するか検索し、存在していなければ、Google側を削除し、同時に文書も削除する。

                          文書を元に、Google側にIDが存在するか検索し、存在していなければ、Notes側を削除し、同時に文書も削除する。



                          あとは更新ですが、これは更新日を同時に文書に保持することで同じ操作が可能と思います。



                          問題は全件検索が必要となる部分ですが、パフォーマンスを考えると避けたいですが難しいですね。

                          • 私も同じイメージです。

                            By Muneyuki Ohkawa 2 decades ago

                            GooCalSyncで用意するNotes DBの中に、以下の3つを作ることで、対応表を実装するものと考えていました。

                            ・フォームを作成し、フィールドとして、iCal UID, Notes UNID, 作成日時, 更新日時を持たせる。

                            ・iCal UIDフィールドでソートされたビューを作成する。

                            ・Notes UNIDフィールドでソートされたビューを作成する。