/*
 * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.micode.notes.gtask.remote;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;

import net.micode.notes.gtask.data.Node;
import net.micode.notes.gtask.data.Task;
import net.micode.notes.gtask.data.TaskList;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.gtask.exception.NetworkFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import net.micode.notes.ui.NotesPreferenceActivity;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;


public class GTaskClient {
    private static final String TAG = GTaskClient.class.getSimpleName();

    private static final String GTASK_URL = "https://mail.google.com/tasks/"; // GTask 的基础 URL

    private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; // 获取 GTask 数据的 URL

    private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; // 提交 GTask 数据的 URL

    private static GTaskClient mInstance = null; // 单例模式，保存 GTaskClient 的唯一实例

    private DefaultHttpClient mHttpClient; // HTTP 客户端

    private String mGetUrl; // 获取 GTask 数据的完整 URL

    private String mPostUrl; // 提交 GTask 数据的完整 URL

    private long mClientVersion; // 客户端版本号

    private boolean mLoggedin; // 是否已登录

    private long mLastLoginTime; // 上次登录时间

    private int mActionId; // 操作 ID

    private Account mAccount; // GTask 帐户

    private JSONArray mUpdateArray; // 待更新的 GTask 数据
    private GTaskClient() {
        mHttpClient = null; // 初始化 HTTP 客户端为 null
        mGetUrl = GTASK_GET_URL; // 初始化获取 GTask 数据的 URL
        mPostUrl = GTASK_POST_URL; // 初始化提交 GTask 数据的 URL
        mClientVersion = -1; // 初始化客户端版本号为 -1
        mLoggedin = false; // 初始化登录状态为 false
        mLastLoginTime = 0; // 初始化上次登录时间为 0
        mActionId = 1; // 初始化操作 ID 为 1
        mAccount = null; // 初始化 GTask 帐户为 null
        mUpdateArray = null; // 初始化待更新的 GTask 数据为 null
    }/*该构造方法用于创建GTaskClient的实例，其中将mHttpClient、mAccount、mUpdateArray等成员变量初始化为 null 或默认值，将mGetUrl和mPostUrl初始化为 GTask 的默认 URL，将mClientVersion初始化为 -1，将mLoggedin初始化为 false，将mLastLoginTime初始化为 0，将mActionId初始化为 1。
    这里使用了默认访问控制符private，意味着该构造方法只能在GTaskClient类内部使用，不能在其他类中创建GTaskClient的实例。*/

    public static synchronized GTaskClient getInstance() {
        if (mInstance == null) { // 如果唯一实例不存在
            mInstance = new GTaskClient(); // 则创建一个新实例
        }
        return mInstance; // 返回唯一实例
    }/*该方法是单例模式的实现，用于获取GTaskClient的唯一实例。在该方法内部，首先判断唯一实例是否已经存在，如果不存在则创建一个新实例，并将其赋值给mInstance。最后返回唯一实例。
    由于该方法可能被多个线程同时调用，所以使用了synchronized关键字来保证在同一时刻只有一个线程能够访问该方法。
    同时，该方法返回的是静态成员变量mInstance，因此可以通过GTaskClient.getInstance()的方式在任意位置获取GTaskClient的唯一实例。*/

    public boolean login(Activity activity) {
        // we suppose that the cookie would expire after 5 minutes
        // then we need to re-login
        final long interval = 1000 * 60 * 5;
        if (mLastLoginTime + interval < System.currentTimeMillis()) {
            mLoggedin = false;
        }

        // need to re-login after account switch
        if (mLoggedin
                && !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity
                .getSyncAccountName(activity))) {
            mLoggedin = false;
        }

        if (mLoggedin) { // 如果已登录，则直接返回
            Log.d(TAG, "already logged in");
            return true;
        }

        mLastLoginTime = System.currentTimeMillis(); // 记录登录时间
        String authToken = loginGoogleAccount(activity, false); // 登录 Google 帐户，获取授权令牌
        if (authToken == null) { // 如果登录失败，则返回 false
            Log.e(TAG, "login google account failed");
            return false;
        }

        // login with custom domain if necessary
        if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase()
                .endsWith("googlemail.com"))) { // 如果不是 Gmail 或 Googlemail 帐户，则使用自定义域名登录
            StringBuilder url = new StringBuilder(GTASK_URL).append("a/"); // 构造自定义域名的 URL
            int index = mAccount.name.indexOf('@') + 1;
            String suffix = mAccount.name.substring(index);
            url.append(suffix + "/");
            mGetUrl = url.toString() + "ig"; // 更新获取 GTask 数据的 URL
            mPostUrl = url.toString() + "r/ig"; // 更新提交 GTask 数据的 URL

            if (tryToLoginGtask(activity, authToken)) { // 尝试登录 GTask
                mLoggedin = true; // 登录成功
            }
        }

        // 如果自定义域名登录失败，则使用 Google 官方 URL 登录
        if (!mLoggedin) {
            mGetUrl = GTASK_GET_URL;
            mPostUrl = GTASK_POST_URL;
            if (!tryToLoginGtask(activity, authToken)) {
                return false;
            }
        }

        mLoggedin = true; // 登录成功
        return true;
    }/*该方法用于登录 GTask，首先检查上次登录时间是否超过 5 分钟，如果超过则需要重新登录，将mLoggedin设置为 false。
    然后判断当前帐户是否发生切换，如果发生切换也需要重新登录，同样将mLoggedin设置为 false。
    如果已经登录，则直接返回 true。否则，记录本次登录时间，然后使用loginGoogleAccount()方法登录 Google 帐户，获取授权令牌。
    如果登录失败，则返回 false。接下来，如果当前帐户不是 Gmail 或 Googlemail 帐户，则使用自定义域名登录，更新获取 GTask 数据和提交 GTask 数据的 URL，然后尝试登录 GTask。
    如果自定义域名登录失败，则使用 Google 官方 URL 登录。
    无论使用哪种方式登录成功，最后将mLoggedin设置为 true，表示已经登录成功。*/

    private String loginGoogleAccount(Activity activity, boolean invalidateToken) {
        String authToken;
        AccountManager accountManager = AccountManager.get(activity); // 获取 AccountManager 实例
        Account[] accounts = accountManager.getAccountsByType("com.google"); // 获取所有 Google 帐户

        if (accounts.length == 0) { // 如果没有可用的 Google 帐户，则返回 null
            Log.e(TAG, "there is no available google account");
            return null;
        }

        String accountName = NotesPreferenceActivity.getSyncAccountName(activity); // 获取设置中的同步帐户名称
        Account account = null;
        for (Account a : accounts) {
            if (a.name.equals(accountName)) { // 如果找到同名帐户，则使用该帐户
                account = a;
                break;
            }
        }
        if (account != null) {
            mAccount = account; // 更新当前使用的帐户
        } else {
            Log.e(TAG, "unable to get an account with the same name in the settings");
            return null;
        }

        // get the token now
        AccountManagerFuture<Bundle> accountManagerFuture = accountManager.getAuthToken(account,
                "goanna_mobile", null, activity, null, null); // 获取 token
        try {
            Bundle authTokenBundle = accountManagerFuture.getResult();
            authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN); // 从 Bundle 中获取 token
            if (invalidateToken) { // 如果需要使 token 失效
                accountManager.invalidateAuthToken("com.google", authToken); // 使 token 失效
                loginGoogleAccount(activity, false); // 重新登录
            }
        } catch (Exception e) { // 获取 token 失败
            Log.e(TAG, "get auth token failed");
            authToken = null;
        }

        return authToken; // 返回 token
    }
    /*该方法的作用是获取 Google 帐户的 token，以用于访问 Google 服务。
    它首先获取所有的 Google 帐户，然后根据设置中的同步帐户名称选择使用哪个帐户。
    接着，它使用AccountManager获取该帐户的 token，并返回该 token。
    如果invalidateToken参数为true，则该方法会使 token 失效，并重新登录，以获取新的 token。
     */

    private boolean tryToLoginGtask(Activity activity, String authToken) {
        if (!loginGtask(authToken)) { // 如果登录 GTask 失败
            // maybe the auth token is out of date, now let's invalidate the
            // token and try again
            authToken = loginGoogleAccount(activity, true); // 使 token 失效并重新登录
            if (authToken == null) { // 如果重新登录失败，则返回 false
                Log.e(TAG, "login google account failed");
                return false;
            }

            if (!loginGtask(authToken)) { // 如果重新登录 GTask 仍然失败，则返回 false
                Log.e(TAG, "login gtask failed");
                return false;
            }
        }
        return true; // 登录 GTask 成功，返回 true
    }
    /*该方法的作用是尝试登录 GTask，它接收一个authToken参数，该参数是通过loginGoogleAccount()方法获取的 Google 帐户的 token。
    如果登录 GTask 失败，则会使 token 失效并重新登录，再次尝试登录 GTask。
    如果重新登录失败，则返回false，否则返回true。
     */

    private boolean loginGtask(String authToken) {
        int timeoutConnection = 10000;
        int timeoutSocket = 15000;
        HttpParams httpParameters = new BasicHttpParams();
        HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
        HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);

        // 设置 HttpClient 的参数
        mHttpClient = new DefaultHttpClient(httpParameters);
        BasicCookieStore localBasicCookieStore = new BasicCookieStore();
        mHttpClient.setCookieStore(localBasicCookieStore);
        HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false);

        // login gtask
        try {
            String loginUrl = mGetUrl + "?auth=" + authToken;
            HttpGet httpGet = new HttpGet(loginUrl); // 创建 HTTP GET 请求
            HttpResponse response = mHttpClient.execute(httpGet); // 执行请求

            // 获取 cookie
            List<Cookie> cookies = mHttpClient.getCookieStore().getCookies();
            boolean hasAuthCookie = false;
            for (Cookie cookie : cookies) {
                if (cookie.getName().contains("GTL")) {
                    hasAuthCookie = true;
                }
            }
            if (!hasAuthCookie) {
                Log.w(TAG, "it seems that there is no auth cookie");
            }

            // 获取客户端版本
            String resString = getResponseContent(response.getEntity());
            String jsBegin = "_setup(";
            String jsEnd = ")}</script>";
            int begin = resString.indexOf(jsBegin);
            int end = resString.lastIndexOf(jsEnd);
            String jsString = null;
            if (begin != -1 && end != -1 && begin < end) {
                jsString = resString.substring(begin + jsBegin.length(), end);
            }
            JSONObject js = new JSONObject(jsString);
            mClientVersion = js.getLong("v");
        } catch (JSONException e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
            return false;
        } catch (Exception e) {
            // simply catch all exceptions
            Log.e(TAG, "httpget gtask_url failed");
            return false;
        }

        return true;
    }
    /*该方法的作用是使用给定的authToken登录 GTask。
    它创建了一个 HTTP GET 请求，并将authToken添加到 URL 末尾，然后执行该请求并获取响应。
    它还获取了响应中包含的 cookie，并将客户端版本存储在mClientVersion中。
    如果登录成功，则返回true，否则返回false。如果发生异常，则返回false。
     */

    private int getActionId() {
        return mActionId++; // 返回下一个 action ID，并将 mActionId 加 1
    }

    private HttpPost createHttpPost() {
        HttpPost httpPost = new HttpPost(mPostUrl); // 创建一个 HTTP POST 请求
        httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); // 设置请求头的 Content-Type
        httpPost.setHeader("AT", "1"); // 设置请求头的 AT 字段为 1
        return httpPost; // 返回创建的 HTTP POST 请求
    }/*getActionId()的作用是获取下一个 action ID，每次调用它都会将mActionId加 1，并返回加 1 后的值。
    createHttpPost()的作用是创建一个 HTTP POST 请求，并设置请求头的 Content-Type 为application/x-www-form-urlencoded;charset=utf-8，设置请求头的 AT 字段为 1。
    它返回创建的 HTTP POST 请求对象。
    */

    private String getResponseContent(HttpEntity entity) throws IOException {
        String contentEncoding = null;
        if (entity.getContentEncoding() != null) {
            contentEncoding = entity.getContentEncoding().getValue(); // 获取响应内容的编码方式
            Log.d(TAG, "encoding: " + contentEncoding);
        }

        InputStream input = entity.getContent(); // 获取响应内容的输入流
        if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) {
            input = new GZIPInputStream(entity.getContent()); // 如果响应内容被 gzip 压缩了，则创建一个 GZIPInputStream 解压缩
        } else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) {
            Inflater inflater = new Inflater(true);
            input = new InflaterInputStream(entity.getContent(), inflater); // 如果响应内容被 deflate 压缩了，则创建一个 InflaterInputStream 解压缩
        }

        try {
            InputStreamReader isr = new InputStreamReader(input); // 创建一个 InputStreamReader 对象
            BufferedReader br = new BufferedReader(isr); // 创建一个 BufferedReader 对象
            StringBuilder sb = new StringBuilder(); // 创建一个 StringBuilder 对象

            while (true) {
                String buff = br.readLine(); // 逐行读取响应内容
                if (buff == null) {
                    return sb.toString(); // 如果读到了末尾，则返回读取到的响应内容
                }
                sb = sb.append(buff); // 将读取到的响应内容追加到 StringBuilder 对象中
            }
        } finally {
            input.close(); // 关闭输入流
        }
    }
    /*getResponseContent(HttpEntity entity)的作用是从HttpEntity对象中获取响应内容，并将其解压（如果响应内容被压缩了）。它返回解压后的响应内容。
    首先获取响应内容的编码方式，然后根据编码方式创建对应的输入流。
    如果响应内容被 gzip 压缩了，则创建一个 GZIPInputStream 对象解压缩;如果响应内容被 deflate 压缩了，则创建一个 InflaterInputStream 对象解压缩。
     接着使用 InputStreamReader 和 BufferedReader 逐行读取响应内容，并将其追加到 StringBuilder 对象中。
     最后返回读取到的响应内容，并关闭输入流。
     */

    private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
        if (!mLoggedin) {
            Log.e(TAG, "please login first");
            throw new ActionFailureException("not logged in");
        }

        HttpPost httpPost = createHttpPost(); // 创建 HTTP POST 请求
        try {
            LinkedList<BasicNameValuePair> list = new LinkedList<BasicNameValuePair>();
            list.add(new BasicNameValuePair("r", js.toString())); // 将传入的 JSONObject 对象转为字符串，并添加到请求参数中
            UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); // 创建一个 UrlEncodedFormEntity 对象，用于封装请求参数
            httpPost.setEntity(entity); // 将 UrlEncodedFormEntity 对象设置为 HTTP POST 请求的实体

            // execute the post
            HttpResponse response = mHttpClient.execute(httpPost); // 执行 HTTP POST 请求
            String jsString = getResponseContent(response.getEntity()); // 获取响应内容，并将其解压（如果响应内容被压缩了），然后转为字符串
            return new JSONObject(jsString); // 将响应内容转为 JSONObject 对象

        } catch (ClientProtocolException e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
            throw new NetworkFailureException("postRequest failed");
        } catch (IOException e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
            throw new NetworkFailureException("postRequest failed");
        } catch (JSONException e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
            throw new ActionFailureException("unable to convert response content to jsonobject");
        } catch (Exception e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
            throw new ActionFailureException("error occurs when posting request");
        }
    }
    /*postRequest(JSONObject js)的作用是向服务器发送 HTTP POST 请求，并将响应内容转为JSONObject对象返回。
    首先检查用户是否已经登录，如果没有登录则抛出异常。然后创建一个 HTTP POST 请求，并将传入的 JSONObject 对象转为字符串，并添加到请求参数中。接着，创建一个 UrlEncodedFormEntity 对象，用于封装请求参数，并将其设置为 HTTP POST 请求的实体。
    执行 HTTP POST 请求，并获取响应内容。将响应内容解压（如果响应内容被压缩了），然后将其转为字符串，并通过 JSONObject 构造方法将其转为 JSONObject 对象。如果转换失败，则抛出异常。
    如果在执行 HTTP POST 请求或转换响应内容为 JSONObject 对象时出现异常，则抛出相应的异常。
    */
    public void createTask(Task task) throws NetworkFailureException {
        commitUpdate(); // 提交所有未提交的更新操作

        try {
            JSONObject jsPost = new JSONObject(); // 创建一个新的 JSONObject 对象
            JSONArray actionList = new JSONArray(); // 创建一个新的 JSONArray 对象，用于存储操作列表

            // action_list
            actionList.put(task.getCreateAction(getActionId())); // 将新增任务的操作添加到操作列表中
            jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 将操作列表添加到 JSONObject 对象中

            // client_version
            jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 添加客户端版本号到 JSONObject 对象中

            // post
            JSONObject jsResponse = postRequest(jsPost); // 向服务器发送 HTTP POST 请求，并获取响应内容
            JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
                    GTaskStringUtils.GTASK_JSON_RESULTS).get(0); // 从响应内容中获取结果列表，并获取第一个结果
            task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); // 获取新任务的 ID，并将其设置为 Task 对象的 gid 属性

        } catch (JSONException e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
            throw new ActionFailureException("create task: handing jsonobject failed");
        }
    }
    /*createTask(Task task)的作用是创建新任务。首先调用commitUpdate()方法提交所有未提交的更新操作。
    然后创建一个新的 JSONObject 对象，并创建一个新的 JSONArray 对象，用于存储操作列表。将新增任务的操作添加到操作列表中，并将操作列表添加到 JSONObject 对象中。将客户端版本号也添加到 JSONObject 对象中。
    向服务器发送 HTTP POST 请求，并获取响应内容。从响应内容中获取结果列表，并获取第一个结果。从结果中获取新任务的 ID，并将其设置为 Task 对象的 gid 属性。
    如果在处理 JSONObject 对象时出现异常，则抛出相应的异常。
    */
    public void createTaskList(TaskList tasklist) throws NetworkFailureException {
        commitUpdate(); // 提交所有未提交的更新操作

        try {
            JSONObject jsPost = new JSONObject(); // 创建一个新的 JSONObject 对象
            JSONArray actionList = new JSONArray(); // 创建一个新的 JSONArray 对象，用于存储操作列表

            // action_list
            actionList.put(tasklist.getCreateAction(getActionId())); // 将新增任务列表的操作添加到操作列表中
            jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 将操作列表添加到 JSONObject 对象中

            // client_version
            jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 添加客户端版本号到 JSONObject 对象中

            // post
            JSONObject jsResponse = postRequest(jsPost); // 向服务器发送 HTTP POST 请求，并获取响应内容
            JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
                    GTaskStringUtils.GTASK_JSON_RESULTS).get(0); // 从响应内容中获取结果列表，并获取第一个结果
            tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); // 获取新任务列表的 ID，并将其设置为 TaskList 对象的 gid 属性

        } catch (JSONException e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
            throw new ActionFailureException("create tasklist: handing jsonobject failed");
        }
    }
    /*createTaskList(TaskList tasklist)的作用是创建新任务列表。首先调用commitUpdate()方法提交所有未提交的更新操作。
    然后创建一个新的 JSONObject 对象，并创建一个新的 JSONArray 对象，用于存储操作列表。将新增任务列表的操作添加到操作列表中，并将操作列表添加到 JSONObject 对象中。将客户端版本号也添加到 JSONObject 对象中。
    向服务器发送 HTTP POST 请求，并获取响应内容。从响应内容中获取结果列表，并获取第一个结果。从结果中获取新任务列表的 ID，并将其设置为 TaskList 对象的 gid 属性。
    如果在处理 JSONObject 对象时出现异常，则抛出相应的异常。
    */
    public void commitUpdate() throws NetworkFailureException {
        if (mUpdateArray != null) { // 判断更新操作列表是否为空
            try {
                JSONObject jsPost = new JSONObject(); // 创建一个新的 JSONObject 对象

                // action_list
                jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray); // 将更新操作列表添加到 JSONObject 对象中

                // client_version
                jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 添加客户端版本号到 JSONObject 对象中

                postRequest(jsPost); // 向服务器发送 HTTP POST 请求，提交更新操作
                mUpdateArray = null; // 更新操作提交成功后，清空更新操作列表

            } catch (JSONException e) {
                Log.e(TAG, e.toString());
                e.printStackTrace();
                throw new ActionFailureException("commit update: handing jsonobject failed");
            }
        }
    }
    /*commitUpdate()的作用是提交所有未提交的更新操作。如果更新操作列表不为空，则创建一个新的 JSONObject 对象，将更新操作列表添加到 JSONObject 对象中，并将客户端版本号也添加到 JSONObject 对象中。
    向服务器发送 HTTP POST 请求，提交更新操作。提交成功后，清空更新操作列表。
    如果在处理 JSONObject 对象时出现异常，则抛出相应的异常。
    */
    public void addUpdateNode(Node node) throws NetworkFailureException {
        if (node != null) { // 判断 Node 对象是否为空
            // too many update items may result in an error
            // set max to 10 items
            if (mUpdateArray != null && mUpdateArray.length() > 10) { // 判断更新操作列表是否已满
                commitUpdate(); // 如果已满，则提交所有未提交的更新操作
            }

            if (mUpdateArray == null)
                mUpdateArray = new JSONArray(); // 如果更新操作列表还未创建，则创建一个新的 JSONArray 对象
            mUpdateArray.put(node.getUpdateAction(getActionId())); // 将 Node 对象的更新操作添加到更新操作列表中
        }
    }
    /*addUpdateNode(Node node)的作用是向更新操作列表中添加一个新的更新操作。首先判断 Node 对象是否为空。
    如果更新操作列表已满（长度大于 10），则调用commitUpdate()方法提交所有未提交的更新操作。
    如果更新操作列表还未创建，则创建一个新的 JSONArray 对象。将 节点
    如果 Node 对象为空，则不执行任何操作。
    */
    public void moveTask(Task task, TaskList preParent, TaskList curParent)
            throws NetworkFailureException {
        commitUpdate(); // 先提交所有未提交的更新操作
        try {
            JSONObject jsPost = new JSONObject(); // 创建一个新的 JSONObject 对象
            JSONArray actionList = new JSONArray(); // 创建一个新的 JSONArray 对象，用于存储更新操作
            JSONObject action = new JSONObject(); // 创建一个新的 JSONObject 对象，用于存储移动任务的操作

            // action_list
            action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
                    GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE); // 设置操作类型为移动任务
            action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); // 设置操作 ID
            action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid()); // 设置任务的 GID

            if (preParent == curParent && task.getPriorSibling() != null) {
                // put prioring_sibing_id only if moving within the tasklist and
                // it is not the first one
                action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling()); // 如果是在同一任务列表中移动任务，则设置前一个任务的 GID
            }

            action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); // 设置任务的原始任务列表的 GID
            action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); // 设置任务的目标任务列表的 GID

            if (preParent != curParent) {
                // put the dest_list only if moving between tasklists
                action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid()); // 如果是在不同的任务列表之间移动任务，则设置任务的目标任务列表的 GID
            }

            actionList.put(action); // 将移动任务的操作添加到更新操作列表中
            jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 将更新操作列表添加到 JSONObject 对象中

            // client_version
            jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 添加客户端版本号到 JSONObject 对象中

            postRequest(jsPost); // 向服务器发送 HTTP POST 请求，提交更新操作

        } catch (JSONException e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
            throw new ActionFailureException("move task: handing jsonobject failed");
        }
    }
    /*moveTask(Task task, TaskList preParent, TaskList curParent)的作用是移动一个任务到另一个任务列表中。
    首先调用commitUpdate()方法提交所有未提交的更新操作。然后，创建一个新的 JSONObject 对象，用于存储移动任务的操作。
    在移动任务的操作中，设置操作类型为移动任务。设置操作 ID 和任务的 GID。如果是在同一任务列表中移动任务，则设置前一个任务的 GID。设置任务的原始任务列表的 GID 和任务的目标任务列表的 GID。如果是在不同的任务列表之间移动任务，则设置任务的目标任务列表的 GID。
    将移动任务的操作添加到更新操作列表中，然后将更新操作列表添加到 JSONObject 对象中。最后，添加客户端版本号到 JSONObject 对象中，向服务器发送 HTTP POST 请求，提交更新操作。
    如果处理 JSONObject 对象时出现异常，则抛出 ActionFailureException 异常。
    */
    public void deleteNode(Node node) throws NetworkFailureException {
        commitUpdate(); // 先提交所有未提交的更新操作
        try {
            JSONObject jsPost = new JSONObject(); // 创建一个新的 JSONObject 对象
            JSONArray actionList = new JSONArray(); // 创建一个新的 JSONArray 对象，用于存储更新操作

            // action_list
            node.setDeleted(true); // 将节点标记为已删除
            actionList.put(node.getUpdateAction(getActionId())); // 将节点的更新操作添加到更新操作列表中
            jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 将更新操作列表添加到 JSONObject 对象中

            // client_version
            jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 添加客户端版本号到 JSONObject 对象中

            postRequest(jsPost); // 向服务器发送 HTTP POST 请求，提交更新操作
            mUpdateArray = null; // 将更新操作数组置为空

        } catch (JSONException e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
            throw new ActionFailureException("delete node: handing jsonobject failed"); // 处理 JSONObject 对象时出现异常，则抛出 ActionFailureException 异常
        }
    }
    /*此方法的主要目的是删除一个节点，并将删除操作提交到服务器。
    方法首先提交所有未提交的更新操作，然后创建一个新的 JSONObject 对象，将节点的删除操作添加到该对象中，同时添加客户端版本号。
    最后，方法通过向服务器发送 HTTP POST 请求来提交更新操作，如果在处理 JSONObject 对象时出现异常，则会抛出 ActionFailureException 异常。
     */

    public JSONArray getTaskLists() throws NetworkFailureException {
        if (!mLoggedin) { // 如果用户没有登录，则抛出 ActionFailureException 异常
            Log.e(TAG, "please login first");
            throw new ActionFailureException("not logged in");
        }

        try {
            HttpGet httpGet = new HttpGet(mGetUrl); // 创建一个新的 HttpGet 请求对象
            HttpResponse response = null;
            response = mHttpClient.execute(httpGet); // 执行 HttpGet 请求

            // get the task list
            String resString = getResponseContent(response.getEntity()); // 获取响应内容
            String jsBegin = "_setup(";
            String jsEnd = ")}</script>";
            int begin = resString.indexOf(jsBegin);
            int end = resString.lastIndexOf(jsEnd);
            String jsString = null;
            if (begin != -1 && end != -1 && begin < end) { // 如果响应内容中包含任务列表，那么提取出来
                jsString = resString.substring(begin + jsBegin.length(), end);
            }
            JSONObject js = new JSONObject(jsString); // 将任务列表转换成 JSONObject 对象
            return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS); // 返回任务列表数组

        } catch (ClientProtocolException e) { // 如果发生协议错误，则抛出 NetworkFailureException 异常
            Log.e(TAG, e.toString());
            e.printStackTrace();
            throw new NetworkFailureException("gettasklists: httpget failed");
        } catch (IOException e) { // 如果发生 I/O 错误，则抛出 NetworkFailureException 异常
            Log.e(TAG, e.toString());
            e.printStackTrace();
            throw new NetworkFailureException("gettasklists: httpget failed");
        } catch (JSONException e) { // 如果处理 JSONObject 对象时出现异常，则抛出 ActionFailureException 异常
            Log.e(TAG, e.toString());
            e.printStackTrace();
            throw new ActionFailureException("get task lists: handing jasonobject failed");
        }
    }
    /*此方法的主要目的是从服务器获取用户的任务列表，并将其作为 JSONArray 对象返回。
    方法首先检查用户是否已登录，然后创建一个新的 HttpGet 请求对象，并通过执行该请求来获取响应内容。
    接下来，方法从响应内容中提取出任务列表，并将其转换为 JSONObject 对象，最后返回任务列表数组。
    如果在执行 HttpGet 请求或处理 JSONObject 对象时发生错误，则会抛出 NetworkFailureException 或 ActionFailureException 异常。
     */

    public JSONArray getTaskList(String listGid) throws NetworkFailureException {
        commitUpdate(); // 提交所有未提交的更改
        try {
            JSONObject jsPost = new JSONObject(); // 创建一个新的 JSONObject 对象
            JSONArray actionList = new JSONArray(); // 创建一个新的 JSONArray 对象
            JSONObject action = new JSONObject(); // 创建一个新的 JSONObject 对象

            // action_list
            action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
                    GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL); // 设置 action 的类型为 "getall"
            action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); // 设置 action 的 ID
            action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid); // 设置 action 操作的任务列表 ID
            action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false); // 设置是否获取已删除的任务
            actionList.put(action); // 将 action 添加到 action_list 中
            jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 将 action_list 添加到 jsPost 中

            // client_version
            jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 设置客户端版本号

            JSONObject jsResponse = postRequest(jsPost); // 发送请求并获取响应
            return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS); // 从响应中获取任务列表并返回

        } catch (JSONException e) { // 如果处理 JSONObject 对象时出现异常，则抛出 ActionFailureException 异常
            Log.e(TAG, e.toString());
            e.printStackTrace();
            throw new ActionFailureException("get task list: handing jsonobject failed");
        }
    }
    /*此方法的主要目的是从服务器获取特定任务列表的任务，并将其作为 JSONArray 对象返回。
    方法首先提交所有未提交的更改，然后构造一个包含获取任务列表的请求并发送它。
    接下来，方法从响应中提取出任务列表，并将其作为 JSONArray 对象返回。
    如果在处理 JSONObject 对象时发生错误，则会抛出 ActionFailureException 异常。
     */

    public Account getSyncAccount() {
        return mAccount;
    }
    //这个方法返回GTaskClient的同步账户（即当前使用的 Google 帐户）。

    public void resetUpdateArray() {
        mUpdateArray = null;
    }
}
    /*这个方法将GTaskClient的更新数组mUpdateArray设置为null，以清除未提交的更改。
    在更新任务列表之前，需要调用commitUpdate()方法提交所有未提交的更改，如果您想丢弃这些更改，可以调用resetUpdateArray()方法以清除它们。
     */