Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

NTFS : Управление квотами (часть II)


Информационный Канал Subscribe.Ru

Программирование для Windows NT на Delphi
Выпуск номер : 6 [ 20 августа 2002 года]


Здравствуйте, уважаемые подписчики.

Сначала хочу извиниться за то, что на прошлой неделе рассылка не вышла - у меня получился вынужденный незапланированный отпуск :). И еще хочу заметить, что прошлый выпуск по номеру был 5ым, а не 4ым, как было написано в заголовке :)

Теперь небольшая работа над ошибками - в прошлом номере был опубликован исходник dskquota.pas. В нем было сделано несколько ошибок. За обнаружение первой спасибо Вячеславу Васильеву.
То, что в Pascal - переменная
интерфейса, в С - указатель на интерфейс. Поэтому в Enum интерфейсах:

 PPEnumDiskQuotaUsers = ^PEnumDiskQuotaUsers;
 PEnumDiskQuotaUsers = ^IEnumDiskQuotaUsers;
 IEnumDiskQuotaUsers = interface(IUnknown)
  ['{7988B577-EC89-11cf-9C00-00AA00A14F56}']
    function Next(
      cUsers  : DWORD;
      rgUsers  : PPDiskQuotaUser;
      var pcUsersFetched : DWORD):HRESULT;stdcall;

    function Skip(cUsers : DWORD):HRESULT;stdcall;
    function Reset():HRESULT;stdcall;
    function Clone(ppEnum : PPEnumDiskQuotaUsers):HRESULT;stdcall;
 end;

Должно быть:

 IEnumDiskQuotaUsers = interface(IUnknown)
  ['{7988B577-EC89-11cf-9C00-00AA00A14F56}']
    function Next(
      cUsers  : DWORD;
      rgUsers  : PDiskQuotaUser; //а не PPDiskQuotaUser, можно
даже out rgUsers: IDiskQuotaUser
      var pcUsersFetched : DWORD):HRESULT;stdcall;

    function Skip(cUsers : DWORD):HRESULT;stdcall;
    function Reset():HRESULT;stdcall;
    function Clone(out ppEnum : IEnumDiskQuotaUsers):HRESULT;stdcall;
//не PPEnum...
 end;

и т.д. по всем ссылкам на PPинтерфейс.

Вторая обнаружилась сама собой - у всех функций должна присутствовать директива stdcall (в одной функции она отсутствовала)

исправленный вариант модуля можно найти по адресу http://www.delphi.xonix.ru/download/dskquota.zip (размер 3Кб)

Сегодня в рассылке:
- Вопросы
- NTFS : Управление дисковыми квотами


Вопросы

Может кто-нибудь подскажет решение (или обходные пути ;) такого вопроса (ответы направляйте автору):

Хотелось бы поиметь задержки менее 1мс, например 1мкс. Вроде знаю направление где копать - получать время исполнения процесса... там вроде приращение идет с дельтой 100нс.
а вот в остальном это темный лес ;)
Зачем это необходимо - да в разных ситуациях. В моем случае - для управления микросхемой через LPT - строб на RD - через 1мкС - считываем данные. Если паузы будут идти кратными 1мс - очень медленная работа.

DeMoN Gardemarin@rambler.ru


Статья
NTFS : Управление дисковыми квотами


Для тех, кто не читал прошлый выпуск, коротко расскажу - дисковые квоты позволяют ограничивать место на диске для определенных пользователей. Для понимания, о чем мы говорим, прочитайте предыдущий выпуск. Найти его можно в архиве рассылки - http://subscribe.ru/archive/comp.soft.prog.de lphint/200208/06163552.html

В этом выпуске - о записях квот для определенных пользователей. Получить список пользователей, которым сопоставлены квоты можно с помощью интерфейса IEnumDiskQuotaUsers. Точнее не список пользователей, а интерфейс IDiskQuotaUser, позволяющий получить и изменять информацию о квотах для определенного пользователя. В моем примере информация о квотах пользователей будет выводиться (как и в закладке "Квоты" | "Записи квот...") в ListView (который в Delphi - TListView). Поэтому для простоты использования создадим класс TUserQuota = class(TListItem).

type
 TUserQuota = class(TListItem)
  private
   fUserID : Cardinal;
  protected
   function  GetQuotaLimit :Int64;
   function  GetThreshold  :Int64;
   function  GetQuotaUsed  :Int64;

   procedure SetQuotaLimit(AValue : Int64);
   procedure SetThreshold (AValue : Int64);
  public

   QuotaInterface : IDiskQuotaUser;

   LogonName   : array [0..100] of WideChar;
   FullName    : array [0..100] of WideChar;

   procedure SetValues;
   procedure Clicked;

   property UserID : Cardinal read fUserID;
   property Limit  : Int64    read GetQuotaLimit write SetQuotaLimit;
   property Threshold : Int64 read GetThreshold  write SetThreshold;
   property QuotaUsed : Int64 read GetQuotaUsed;
 end;

Как видно из описания, этот класс содержит (указатель на) интерфейс IDiskQuotaUser и несколько свойств и функций, упрощающих работу с этим интерфейсом. К реализации этого класса мы вернемся позже.

Получить интерфейс IEnumDiskQuotaUsers можно функцией CreateEnumUsers интерфейса IDiskQuotaControl. Третьим параметром функции является fNameResolution, определяющий, как будет получаться информация. Определены 2 типа DISKQUOTA_USERNAME_RESOLVE_SYNC и DISKQUOTA_USERNAME_RESOLVE_ASYNC (третий - DISKQUOTA_USERNAME_RESOLVE_NONE нас не интересует). Разница между этими типами проявляется при получении следующих "записей" функцией IEnumDiskQuotaUsers.Next(). Рассмотрим, что происходит поподробнее.

Вполне логично, что в записях квот используются только SID, а имя пользователя и домена уже определяется по нему. Выполняется функция Next IEnumDiskQuotaUsers. Получается SID пользователя следующей записи и по нему определяется имя и домен пользователя (а процедура эта не самая быстрая). Вот тут и проявляется разница - в случае DISKQUOTA_USERNAME_RESOLVE_SYNC функция Next ждет пока будет обнаружено имя пользователя (а имя пользователя ищется и в соседних доменах и эта процедура может занимать несколько секунд), а при DISKQUOTA_USERNAME_RESOLVE_ASYNC функция возвращает результат немедленно, даже если функция не может сразу найти имя пользователя. При этом, если в полученном интерфейсе IDiskQuotaUser выполнить GetAccountStatus(Status), то Status <> DISKQUOTA_USER_ACCOUNT_RESOLVED.
Логично было бы предположить, что если запрос на получение имени пользователя был "отправлен", то через некоторое время можно будет получить имя этого пользователя. Для получения этого события используется интерфейс IDiskQuotaEvents, который должна реализовывать ваша программа, в случае асинхронного (DISKQUOTA_USERNAME_RESOLVE_ASYNC) получения информации (а дальше мы будем рассматривать только этот вариант как более сложный).

Примечание. Если имя пользователя уже ранее получалось и находится в кэше, то оно берется именно из него и разницы в скорости между двумя вариантами нет. Также нет большой разницы в случае, если все пользователи локальные. Но это частные случаи.

Вернемся к реализации интерфейса IDiskQuotaEvents. Реализовать его можно так:

type
  TForm1 = class(TForm,IDiskQuotaEvents)
  //. пропущено ...
  public
    { Public declarations }
    CPointContainer : IConnectionPointContainer;
    CPoint : IConnectionPoint;
    QEvents : IDiskQuotaEvents;

    dwCookie : Integer;

 // Единственная функция интерфейса IDiskQuotaEvents
    function  OnUserNameChanged(pUser : IDiskQuotaUser):HRESULT;stdcall;
  end;

Функцию OnUserNameChanged система будет вызывать, когда сможет (или не сможет из-за ошибки) получить имя пользователя. Естественно у этой функции должна быть реализация, но мы оставим ее пока пустой

function TForm1.OnUserNameChanged(pUser : IDiskQuotaUser):HRESULT;
begin
 // крайне не рекомендуется делать это сообщение модальным
 // (т.е. вместо 0 использовать Form1.Handle)
 MessageBox(0,'OnUserNameChanged','System',MB_OK);
end;

Теперь надо сообщить системе, что у нас есть нужный интерфейс. Для этого IDiskQuotaControl наследует интерфейс IConnectionPointContainer.

Это надо писать где-нибудь после Quota.Initialize (т.е. после инициализации)

 // Получаем интерфейс IConnectionPointContainer
 Res:=Quota.QueryInterface(IConnectionPointContainer,CPointContainer);
 if FAILED(Res) then ShowMessage('IConnectionPointContainer Error');

 // Получаем интерфейс IConnectionPoint соответствующий IDiskQuotaEvents
 Res:=CPointContainer.FindConnectionPoint(IDiskQuotaEvents,CPoint);
 if FAILED(Res) then ShowMessage('FindConnectionPoint Error');

 // Получаем интерфейс IDiskQuotaEvents (который мы реализовали)
 Res:=Self.QueryInterface(IDiskQuotaEvents,QEvents);
 if FAILED(Res) then ShowMessage('Self.QueryInterface Error');

 // Подписываемся на события
 Res:=CPoint.Advise(QEvents,dwCookie);
 if FAILED(Res) then ShowMessage('CPoint.Advise Error');


Ну а теперь перейдем к основной части - получению списка записей (читать интерфейсов IDiskQuotaUser) квот.

procedure TForm1.GetQuotaEntriesList;
var
 Res : Cardinal;
 Enum : IEnumDiskQuotaUsers;

 ui : TUserQuota;
begin
 // Создаем объект, содержащий IEnumDiskQuotaUsers
 Res:=Quota.CreateEnumUsers(nil,0,DISKQUOTA_USERNAME_RESOLVE_ASYNC,Enum);
 if FAILED(Res) then ShowMessage('CreateEnumUsers Error');

 repeat
  // Создаем TUserQuota (он же потомок TListItem)
  ui := TUserQuota.Create(LV.Items);
  // LV как можно догадаться TListView

  // Перечисляем записи по одной
  // Признак ошибки - результат не равен нулю.
  if Enum.Next(1,UI.QuotaInterface,nil)<>0 then Break;

  // Добавляем элемент списка в ListView
  // и устанавливаем значения функцией SetValue (реализация ниже)
  (LV.Items.AddItem(UI) as TUserQuota).SetValues();
 until false;

 // Ну здесь можно было бы сделать ui.Free;
 // т.к. мы создаем его до вызова Next (и получения ею результата)
end;

Вот и все перечисление :) В результате мы получим несколько пустых строк в ListView и возможно несколько сообщений из OnUserNameChanged. Пустые строки получатся из-за того, что мы еще не реализовали наш самодельный TUserQuota (вообще-то не реализовав этот класс с кодом выше мы бы получили ошибку при компиляции :))

/ Устанавливаем значения
procedure TUserQuota.SetValues();
var
 i : Integer;
begin
 // Получаем ID пользователя
 QuotaInterface.GetID(fUserID);

 // получаем логин и полное имя пользователя
 QuotaInterface.GetName(nil,0,@LogonName[0],100,@FullName[0],100);

 // устанавливаем заголовок элемента равным логину
 Caption:=LogonName;

 // Добавляем текст в первый столбец ListView
 // (установите ViewStyle в vsReport)
 SubItems.Add(FullName);

 // Создаем еще 3 столбца
 // они будут заполнены функциями ниже
 for i:=1 to 3 do SubItems.Add('');

 GetQuotaLimit;
 GetThreshold;
 GetQuotaUsed;
end;


// устанавливает значение квоты пользователя
procedure TUserQuota.SetQuotaLimit(AValue : Int64);
begin
 QuotaInterface.SetQuotaLimit(AValue,TRUE);
end;

// получает численное значение квоты
// и выводит ее текстовое значение во 2 столбец
function TUserQuota.GetQuotaLimit:Int64;
var
 // 150 символов более чем достаточно
 Text : array [0..150] of WideChar;
begin
 // Числовое значение
 QuotaInterface.GetQuotaLimit(Result);

 // текстовое
 QuotaInterface.GetQuotaLimitText(Text,150);
 SubItems.Strings[1]:=Text;
end;

// устанавливает значение порога предупреждения
procedure TUserQuota.SetThreshold(AValue : Int64);
begin
 QuotaInterface.SetQuotaThreshold(AValue,TRUE);
end;

// получает значение порога предупреждения
function TUserQuota.GetThreshold():Int64;
var
 Text : array [0..150] of WideChar;
begin
 QuotaInterface.GetQuotaThreshold(Result);
 QuotaInterface.GetQuotaThresholdText(Text,150);
 SubItems.Strings[2]:=Text;
end;

// получает размер файлов пользователя на диске
function TUserQuota.GetQuotaUsed:Int64;
var
 Text : array [0..150] of WideChar;
begin
 QuotaInterface.GetQuotaUsed(Result);
 QuotaInterface.GetQuotaUsedText(Text,150);
 SubItems.Strings[3]:=Text;
end;

// Это просто "заглушка" функции, которая будет вызываться
// при даблклике на элементе
procedure TUserQuota.Clicked();
begin
 // в оригинале здесь диалоговое окно для изменения параметров
 MessageBox(0,FullName,LogonName,MB_OK or MB_ICONINFORMATION);
end; 

Теперь состояние квоты пользователя можно менять например так
UserQuota.Limit:=100000; //в байтах


Ну и последнее - реализация OnUserNameChanged.

function TForm1.OnUserNameChanged(pUser : IDiskQuotaUser):HRESULT;
var
 cbSid : Cardinal;
 Sid : PSID;
 StrSid : PWideChar;

 ID : Cardinal;

 i : Integer;
 Status : Cardinal;
begin
 // Возвращаемое значение игнорируется.
 Result:=0;

 if not Assigned(pUser) then begin ShowMessage('Error'); exit;end;

 // получаем ID пользователя
 pUser.GetID(ID);

 // А вот для этого собственно и был использован класс TUserQuota
 // вместо обычного TListItem

 // Перебираем все элементы TListView
 for i:=0 to LV.Items.Count-1 do
  begin
   // ищем такой-же ID, как и у этого интерфейса
   try
    with (LV.Items.Item[i] as TUserQuota) do
     begin
      if UserID = ID
       then begin
     // Проверяем статус
        pUser.GetAccountStatus(Status);
  // Если имя пользователя мы не получили,
  // то выводим его SID
        if Status<>DISKQUOTA_USER_ACCOUNT_RESOLVED
         then begin
    // получаем размер SID
          pUser.GetSidLength(cbSid);

    // Выделяем память и получаем SID
          GetMem(Sid,cbSid);
          pUser.GetSid(Sid,cbSid);

    // Получаем и выводим текстовый SID
          ConvertSidToStringSidW(Sid,StrSid);
          Caption:=StrSid;

    // очищаем память
          LocalFree(Cardinal(StrSid));
    FreeMem(Sid);
         end;
        end;
     end;
   except
    // мало ли чего может быть в TListView вместо TUserQuota
 // например, тот же TListItem ;) и в результате Exception
    on EInvalidCast do begin end;
   end;
  end;
end;

И еще - как вы могли заметить, в списке записей квот находятся не все пользователи, а только те, чьи файлы есть на интересующем нас диске. Запись создается автоматически при первой записи пользователем информации на диск. При этом ему назначаются размеры квот по умолчанию.
Если вам необходимо вручную добавить пользователя, то воспользуйтесь функциями AddUserName(добавление по имени) или AddUserSid(по SID) интерфейса IDiskQuotaControl. Также по этим параметрам можно реализовать поиск (FindUserName и FindUserSid). Удаляется запись функцией DeleteUser.

На этом я заканчиваю описание работы с квотами. Если будут вопросы или найдете ошибки, то пишите на flint@vtc.ru
Вот и все на сегодня.
С уважением, ведущий рассылки FliNT




http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу

В избранное