Skip to content

系统日历数据源读取失败时回退到 MockCalendarService,导致显示虚假日程;且不支持 Outlook classic (Exchange/MAPI) #2

Description

@littlepure

[Bug] 系统日历数据源读取失败时回退到 MockCalendarService,导致显示虚假日程;且不支持 Outlook classic (Exchange/MAPI)

摘要

当用户选择"系统日历"或"两者都用 (Both)"作为数据源时,WinCal 会显示一批完全不存在的虚构日程(标题如"客户电话"、"代码审查"、"团队周会"、"产品评审",日历标为 "Outlook" / "Google Calendar" / "iCloud",地点为"会议室 A")。

这些数据并非来自用户真实的系统日历,而是 MockCalendarService 生成的假数据。根本原因有两个:

  1. BugCalendarViewModel.CreateSystemService() 在拿不到 WindowsCalendarService 类型时,错误地回退到了 MockCalendarService(本该回退到空的 EmptyCalendarService)。
  2. 功能缺失:WinCal 只读取 WinRT 的 Windows.ApplicationModel.Appointments(即 Windows 自带"邮件和日历"App 的日历仓库),不支持 Outlook classic(Exchange/MAPI,数据存于 .ost 文件)。大量用户(尤其是企业用户)的真实日程在 Outlook classic 里,WinCal 完全读不到。

两者叠加,造成"用户有真实日程(在 Outlook classic 里)却读不到,反而显示一堆虚构日程"的荒谬现象。

环境

  • WinCal 版本:1.3(scavin/WinCal main 分支,commit 1346dcc
  • 操作系统:Windows 11
  • 账户情况:仅使用 Outlook classic 连接公司 Exchange 账户(@corp.example.com),未在 Windows"邮件和日历"App 中添加任何账户

复现步骤

  1. 安装并启动 WinCal。
  2. 打开设置,将"日历数据源"设为 "系统日历""两者都用 (Both)"
  3. 观察日历面板中的事件列表 / 日期圆点。

实际结果:出现一批随机、不存在的日程("客户电话"、"代码审查"、"季度总结"、"1:1 会议"等),且日历来源显示为 "Outlook" / "Google Calendar" / "iCloud",地点为"会议室 A"。

预期结果:要么显示空的、要么显示用户真正的系统日历日程,绝不应显示随机生成的假日程。

根因分析

1. MockCalendarService 兜底 bug(显示虚假日程的直接原因)

Core/Services/MockCalendarService.cs 中的标题、日历名、地点为:

var titles = new[] {
    "团队周会", "产品评审", "季度总结", "1:1 会议", "午餐",
    "客户电话", "代码审查", "设计讨论", "项目排期", "培训"
};
var calendars = new[] { "Outlook", "Google Calendar", "iCloud" };
// ...
Location: rng.Next(3) < 1 ? "会议室 A" : "",

这些字面量与用户报告的"虚构日程"一一对应

ViewModels/CalendarViewModel.csCreateSystemService() 中:

private static ICalendarService CreateSystemService()
{
    try
    {
        var type = Type.GetType("WinCal.Core.Services.WindowsCalendarService, WinCal");
        if (type != null)
            return (ICalendarService)Activator.CreateInstance(type)!;
    }
    catch { } // WinRT not available
    return new MockCalendarService();   // ← 错误:生产环境兜底不应返回 Mock
}

WindowsCalendarService 类型反射不到(或初始化抛出异常)时,应返回 EmptyCalendarService(空数据),却返回了 MockCalendarService(随机假数据)MockCalendarService 明确标注为"开发调试用",不应出现在生产代码路径中。

2. 不支持 Outlook classic(真实日程读不到的原因)

WindowsCalendarService 通过 AppointmentManager.RequestStoreAsync(...) 读取的是 WinRT AppointmentStore,即 Windows 自带"邮件和日历"App 的数据仓库。

而 Outlook classic 的日程数据存储于 .ost(MAPI/Exchange)文件,属于另一套完全独立、互不同步的仓库。因此:

  • 用户真实日程(Outlook classic / Exchange)→ 存在于 .ost → WinCal 读不到
  • WinCal 能读到的 AppointmentStore → 可能为空 → 触发上面的 Mock 兜底 → 显示假数据。

修复建议

针对 Bug 1(必须修复)

CreateSystemService() 的兜底返回从 MockCalendarService 改为 EmptyCalendarService

private static ICalendarService CreateSystemService()
{
    try
    {
        var type = Type.GetType("WinCal.Core.Services.WindowsCalendarService, WinCal");
        if (type != null)
            return (ICalendarService)Activator.CreateInstance(type)!;
    }
    catch { }
    return new EmptyCalendarService();   // 修正:拿不到系统日历时返回空,而非造假数据
}

同时建议:MockCalendarService 应仅通过编译开关(如 #if DEBUG)或显式配置启用,避免任何生产环境误用。

针对功能缺失 2(增强请求)

考虑增加对 Outlook classic 日历的读取支持,可通过以下方式之一:

  • Outlook COM Interop:通过 Outlook.Application + Namespace.GetDefaultFolder(olFolderCalendar) 读取用户 Exchange/本地日历。
  • MAPI(如 MFCMAPI / Redemption 库)直接读取 .ost 中的日历项。
  • 或者,在设置界面提供明确的提示:"当前系统日历('邮件和日历'App)为空;若你的日程在 Outlook classic 中,请使用 ICS 订阅或将其账户添加到 Windows'邮件和日历'App"。

至少应在系统日历为空/不可用且未配置任何数据源时,向用户明确提示原因,而不是静默显示 Mock 假数据。

补充证据

通过 PowerShell 调用相同的 WinRT API 可复现:系统 AppointmentStore 中仅有一个空日历(SourceDisplayName=Microsoft 账户LocalId=b,5,13),近期无任何日程;而用户机器上存在多个 Exchange .ost 文件(真实日程所在),确认两套数据互不相通。


相关代码位置:

  • Core/Services/MockCalendarService.cs
  • Core/Services/EmptyCalendarService.cs
  • ViewModels/CalendarViewModel.csCreateSystemService
  • Core/Services/WindowsCalendarService.cs

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions